package Monitor;

import Common.Quadrado;
import Common.EstadoCoelho;
import Common.EstadoCenoura;
import Common.Atributos;
import Viewer.Outdoor;
import java.util.Random;
import Log.Logger;

public class Prado implements Runnable{

    private Quadrado[][] prado;
    private Atributos info;
    private EstadoCoelho[] RabbitList;
    private EstadoCenoura[] CarrotList;
    private Thread[] TheRabbits;
    private Thread[] TheCarrots;
    private Outdoor field;
    private int rows, columns;
    private Logger log;
    private boolean endFlag = false;
    
    public Prado (int Rows, int Columns, int Nr, Outdoor temp, int values[], Thread[] Coelhos, Thread[] Cenouras) {
        int idgen = 1;

        this.rows = Rows; 
        this.columns = Columns;

        TheRabbits = Coelhos;
        TheCarrots = Cenouras;
        prado = new Quadrado[this.columns][this.rows];

        info = new Atributos(values[0], values[1], values[2], values[3], values[4], values[5], values[6], values[7], values[8], values[9], values[10]);
        RabbitList = new EstadoCoelho[Nr];
        CarrotList = new EstadoCenoura[rows*columns];
        field = temp;

        field.setTask("Creating Field: ");
        field.setMax(Nr + CarrotList.length + this.columns*this.rows);

        for(int r=0; r<Nr; r++) {
            RabbitList[r] = EstadoCoelho.Wandering;
            field.doStep();
        }
        for(int c=0; c<CarrotList.length; c++) {
            CarrotList[c] = EstadoCenoura.Seed;
            field.doStep();
        }
        for(int i=0; i<this.columns; i++)
            for(int j=0; j<this.rows; j++) {
                prado[i][j] = new Quadrado(idgen, 0, 0, 0, 0);
                idgen++;
                field.doStep();
            }
        
        field.setTask(null);

        log=new Logger(1,"Prado");
    }

    /**
     * Função que verifica se uma quadricula tem ou não uma ou mais sementes.
     * @param x - coordenada X.
     * @param y - coordenada Y.
     * @return Valor boolean - true se houver sementes e false se não houver.
     */
    public synchronized boolean hasSeeds(int x, int y) {
        return prado[x][y].temSeeds();
    }

    /**
     * Função que verifica se uma quadricula tem ou não uma cenoura.
     * @param x - coordenada X.
     * @param y - coordenada Y.
     * @return Valor boolean - true se houver cenoura e false se não houver.
     */
    public synchronized boolean hasCarrot(int x, int y) {
        return prado[x][y].existeCenoura();
    }

    /**
     * Função que retorna a energia da cenoura numa quadricula.
     * @param x - coordenada X.
     * @param y - coordenada Y.
     * @return Valor inteiro conrrespondente à energia da cenoura
     */
    public synchronized int carrotEnergy(int x, int y) {
        int ret =  prado[x][y].getCenouraEnergia();
        prado[x][y].setId_cenoura(0);
        prado[x][y].setEnergia_cenoura(0);
        return ret;
    }

    /**
     * Função que avalia a possibilidade de salto dum coelho.
     * @param id_rabbit - ID do coelho
     * @param oldX - antiga coordenada X
     * @param oldY - antiga coordenada Y
     * @param newX - nova coordenada X
     * @param newY - nova coordenada Y
     * @return Valor boolean - true se puder saltar e false se não puder
     */
    public synchronized boolean canJump(int id_rabbit, int oldX, int oldY, int newX, int newY) {
        if((newX >= 0) && (newX < this.columns) && (newY >= 0) && (newY < this.rows)) {
            if(!prado[newX][newY].temCoelho())
            {
                prado[newX][newY].setId_coelho(id_rabbit);
                field.updateField(newX, newY, true, prado[newX][newY].existeCenoura(), prado[newX][newY].temSeeds());
                prado[oldX][oldY].setId_coelho(0);
                field.updateField(oldX, oldY, false, prado[oldX][oldY].existeCenoura(), prado[oldX][oldY].temSeeds());
                return true;
            }
            else
                return false;
        }
        else
            return false;
    }

    /**
     * Função que avalia a possibilidade de germinação de uma semente.
     * @param idcarrot - ID da cenoura
     * @param x - coordenada X
     * @param y - coordenada Y
     * @return Valor boolean - true se puder germinar e false se não puder
     */
    public synchronized boolean canGerminate(int idcarrot, int x, int y) {
        if(prado[x][y].temSeeds()) {
            prado[x][y].decSeeds();
            prado[x][y].setId_cenoura(idcarrot);
            return true;
        }
        else
            return false;
    }

    /**
     * Função que avalia a presença de pollen para a cenoura se reproduzir.
     * @param x - coordenada X
     * @param y - coordenada Y
     * @return Valor boolean - true se houver pollen e false se não houver
     */
    public synchronized boolean hasPollen(int x, int y) {
        for(int i=x-1; i<x+2; i++)
            for(int j=y-1; j>y+2;j++)
                if(!((i == x) && (j == y)))
                    if((CarrotList[(j * this.columns) + i] == EstadoCenoura.Flower) || (CarrotList[x * y + this.columns] == EstadoCenoura.Seeding))
                        return true;
        return false;
    }

    /**
     * Função que recebe a semente da cenoura e coloca-a no quadrado escolhido.
     * @param x - coordenada X
     * @param y - coordenada Y
     */
    public synchronized void plantSeed(int x, int y) {
        prado[x][y].incSeeds();
        field.updateField(x, y, prado[x][y].temCoelho(), prado[x][y].existeCenoura(), true);
        if(!prado[x][y].existeCenoura()) {
            CarrotList[(y*this.columns)+x] = EstadoCenoura.Seed;
            prado[x][y].setId_cenoura((y*this.columns)+x+1);
        }
    }

    /**
     * Função que informa o prado sobre alterações num coelho.
     * @param id_rabbit - ID do coelho
     * @param state - estado do coelho
     * @param x - coordenada X
     * @param y - coordenada Y
     */
    public synchronized void informCoelho(int id_rabbit, EstadoCoelho state, int x, int y) {
        RabbitList[id_rabbit-1] = state;
        if(state == EstadoCoelho.Dead) {
            field.updateField(x, y, false, prado[x][y].existeCenoura(), prado[x][y].temSeeds());
            prado[x][y].setId_coelho(0);
        }
    }

    /**
     * Função que informa o prado sobre alterações numa cenoura.
     * @param id_carrot - ID da cenoura
     * @param state - estado da cenoura
     * @param x - coordenada X
     * @param y - coordenada Y
     */
    public synchronized void informCenoura(int id_carrot, int energy, EstadoCenoura state, int x, int y) {
        CarrotList[id_carrot-1] = state;
        if((state == EstadoCenoura.Dead) || (state == EstadoCenoura.Seed) || (state == EstadoCenoura.Terminated)) {
            field.updateField(x, y, prado[x][y].temCoelho(), false, prado[x][y].temSeeds());
            prado[x][y].setId_cenoura(0);
            prado[x][y].setEnergia_cenoura(0);
        }
        else if(state == EstadoCenoura.Growing) {
            field.updateField(x, y, prado[x][y].temCoelho(), true, prado[x][y].temSeeds());
            prado[x][y].setId_cenoura(id_carrot);
            prado[x][y].setEnergia_cenoura(energy);
        }
    }

    /**
     * Função que devolve aos coelhos e cenouras o valor da flag de fim forçado pelo utilizador.
     * @return Valor boolean - true se o fim for accionado e false se não for
     */
    public synchronized boolean getEndFlag() {
        return this.endFlag;
    }

    /**
     * Função que força o fim dos coelhos e cenouras.
     */
    private synchronized void destroyAll() {
        field.setTask("Cleaning Field: ");
        field.setMax(columns * rows + TheRabbits.length + TheCarrots.length);
        //Activar flag de terminação
        this.endFlag = true;
        //Esperar pelo fim dos threads
        try {
            Thread.currentThread().sleep(2500);
        }
        catch(InterruptedException ex) {}

        //Eliminar Coelhos
        for(int i=0; i<TheRabbits.length; i++) {
            TheRabbits[i] = null;
            field.doStep();
        }
        //Eliminar Cenouras
        for(int i=0; i<TheCarrots.length; i++) {
            TheCarrots[i] = null;
            field.doStep();
        }

        //Limpar Quadrados
        for(int i=0; i<columns; i++)
            for(int j=0; j<rows; j++) {
                prado[i][j].setN_sementes(0);
                prado[i][j].setId_coelho(0);
                prado[i][j].setId_cenoura(0);
                prado[i][j].setEnergia_cenoura(0);
                field.doStep();
            }
        field.setTask(null);
    }

    private boolean checkSim() {
        boolean retVal = false;

        for(int i=0; i<RabbitList.length; i++)
            if(RabbitList[i] != EstadoCoelho.Dead)
                retVal = true;

        for(int i=0; i<CarrotList.length; i++)
            if((CarrotList[i] != EstadoCenoura.Dead) && (CarrotList[i] != EstadoCenoura.Terminated) && (CarrotList[i] != EstadoCenoura.Seed))
                retVal = true;
        
        for(int i=0; i<columns; i++)
            for(int j=0; j<rows; j++)
                if(prado[i][j].getN_sementes() != 0)
                    retVal = true;

        return retVal;
    }

    public void run () {
        //Localizar seeds aleatóriamente
        Random siteChooser = new Random();
        for(int i=0; i<((info.getSmax()-info.getSmin())/2+info.getSmin()); i++) {
            prado[siteChooser.nextInt(columns)][siteChooser.nextInt(rows)].incSeeds();
        }

        /**
         * Verificar continuamente o estado da simulação para detectar
         * automaticamente o seu fim.
         */
        while(checkSim()) {
            if(field.getFlag()) {
                destroyAll();
                field.endMessage(1);
                return;
            }
            for (int i=0;i<rows;i++){
                for (int j=0;j<columns;j++){
                    if ((prado[i][j].getId_cenoura()!=0) || (prado[i][j].getId_coelho()!=0) || (prado[i][j].getN_sementes()!=0) )
                    log.Log("Quadrado " + prado[i][j].getId_quadrado() +
                            " ||Cenoura Id: " + prado[i][j].getId_cenoura()+
                            " || Coelho Id: " + prado[i][j].getId_coelho()+
                            " || Numero Sementes: " + prado[i][j].getN_sementes());
                }
            }
        }
        field.endMessage(0);
        field.gameOver();
    }

}

